tests/libtest: Just use python as a webserver if no libsoup
authorColin Walters <walters@verbum.org>
Sun, 1 Jun 2025 21:00:01 +0000 (17:00 -0400)
committerColin Walters <walters@verbum.org>
Wed, 4 Jun 2025 17:58:27 +0000 (13:58 -0400)
We only have a very few tests that actually need what
we have in ostree-trivial-httpd that supports things like serving
random 500 errors etc.

If we don't have libsoup, then just use a python webserver.

Signed-off-by: Colin Walters <walters@verbum.org>
Makefile-tests.am
tests/libtest.sh
tests/test-pull-basicauth.sh
tests/test-pull-repeated.sh
tests/test-pull-resume.sh
tests/test-remote-headers.sh
tests/webserver.py [new file with mode: 0755]

index 475080ed0784e562cedddf5e303689eac6fa96a4..14b5fee3e6e3e0544d7c2fda0adf67a478ada3da 100644 (file)
@@ -473,14 +473,6 @@ dist_test_scripts += $(_installed_or_uninstalled_test_scripts)
 test_programs += $(_installed_or_uninstalled_test_programs)
 endif
 
-if !USE_LIBSOUP_OR_LIBSOUP3
-no-soup-for-you-warning:
-       @echo "WARNING: $(PACKAGE) was built without libsoup, which is currently" 1>&2
-       @echo "WARNING: required for many unit tests." 1>&2
-       sleep 10
-check: no-soup-for-you-warning
-endif
-
 # Unfortunately the glib test data APIs don't actually handle
 # non-recursive Automake, so we change our code to canonically look
 # for tests/ which is just a symlink when installed.
index e1eb137719212dbaa83b7ef6b50755e18371c09c..52fa946d6ec84810def8a46e34da8fa1eecd67b3 100755 (executable)
@@ -285,6 +285,26 @@ ostree_repo_init() {
     fi
 }
 
+run_webserver() {
+    echo httpd=${OSTREE_HTTPD}
+    if test -z "${OSTREE_HTTPD:-}"; then
+        if test "$#" -gt 0; then
+            echo "fatal: unhandled arguments for webserver: $@" 1>&2
+            exit 1
+        fi
+        # Note this automatically daemonizes; close stdin to ensure it doesn't leak to the child.
+        ${test_srcdir}/webserver.py ${test_tmpdir}/httpd-port </dev/null &>/dev/null
+        echo "Waiting for webserver..."
+        while test '!' -f ${test_tmpdir}/httpd-port; do
+            sleep 0.5
+        done
+    else
+        ${OSTREE_HTTPD} --autoexit --log-file $(pwd)/httpd.log --daemonize -p ${test_tmpdir}/httpd-port "$@"
+    fi
+    port=$(cat ${test_tmpdir}/httpd-port)
+    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
+}
+
 # The original one; use setup_fake_remote_repo2 for newer code,
 # down the line we'll try to port tests.
 setup_fake_remote_repo1() {
@@ -316,9 +336,7 @@ setup_fake_remote_repo1() {
     mkdir ${test_tmpdir}/httpd
     cd httpd
     ln -s ${test_tmpdir}/ostree-srv ostree
-    ${OSTREE_HTTPD} --autoexit --log-file $(pwd)/httpd.log --daemonize -p ${test_tmpdir}/httpd-port "$@"
-    port=$(cat ${test_tmpdir}/httpd-port)
-    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
+    run_webserver "$@"
     cd ${oldpwd} 
 
     export OSTREE="${CMD_PREFIX} ostree --repo=repo"
@@ -361,10 +379,8 @@ setup_fake_remote_repo2() {
     mkdir ${test_tmpdir}/httpd
     cd httpd
     ln -s ${test_tmpdir}/ostree-srv ostree
-    ${OSTREE_HTTPD} --autoexit --log-file $(pwd)/httpd.log --daemonize -p ${test_tmpdir}/httpd-port $args
-    port=$(cat ${test_tmpdir}/httpd-port)
-    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
-    cd ${oldpwd}
+    run_webserver
+    cd ${oldpwd} 
     export OSTREE="${CMD_PREFIX} ostree --repo=repo"
 }
 
@@ -411,7 +427,11 @@ setup_os_repository () {
     shift
     bootmode=$1
     shift
-    bootdir=${1:-usr/lib/modules/3.6.0}
+    bootdir=usr/lib/modules/3.6.0
+    if test "$#" -gt 0; then
+        bootdir=$1
+        shift
+    fi
 
     oldpwd=`pwd`
 
@@ -527,9 +547,7 @@ EOF
     mkdir ${test_tmpdir}/httpd
     cd httpd
     ln -s ${test_tmpdir} ostree
-    ${OSTREE_HTTPD} --autoexit --daemonize -p ${test_tmpdir}/httpd-port
-    port=$(cat ${test_tmpdir}/httpd-port)
-    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
+    run_webserver "$@"
     cd ${oldpwd} 
 }
 
@@ -614,6 +632,12 @@ skip_one_without_user_xattrs () {
     fi
 }
 
+skip_without_ostree_httpd () {
+    if test -z "${OSTREE_HTTPD:-}"; then
+        skip "this test requires libsoup (ostree-trivial-httpd)"
+    fi
+}
+
 skip_without_user_xattrs () {
     if ! have_user_xattrs; then
         skip "this test requires xattr support"
index 682873555f2df532703f5e1a4f66a835f713be6f..bef916af39364048917e6f7f228d6cc982ea424b 100755 (executable)
@@ -19,6 +19,7 @@ set -euo pipefail
 
 . $(dirname $0)/libtest.sh
 
+skip_without_ostree_httpd
 setup_fake_remote_repo1 "archive" "" "--require-basic-auth"
 
 echo '1..3'
index b3e0074207e56482f785e41dfb17eff380dcf954..51113b6601399a6021145d262db5f570771a4004 100755 (executable)
@@ -21,6 +21,8 @@ set -euo pipefail
 
 . $(dirname $0)/libtest.sh
 
+skip_without_ostree_httpd
+
 COMMIT_SIGN=""
 if has_ostree_feature gpgme; then
     COMMIT_SIGN="--gpg-homedir=${TEST_GPG_KEYHOME} --gpg-sign=${TEST_GPG_KEYID_1}"
index d918578741cf9f9f06507375f9d515fc07ab887e..ec8a3bf412e37c4da3af07c97b1027973e23f132 100755 (executable)
@@ -21,6 +21,7 @@ set -euo pipefail
 
 . $(dirname $0)/libtest.sh
 
+skip_without_ostree_httpd
 setup_fake_remote_repo1 "archive" "" "--force-range-requests"
 
 echo '1..1'
index d3bf4f9728916ba0a19b7c580c674691a6908e93..9648ef8c34590b588d4f074cbee4a719697ab4bb 100755 (executable)
 
 set -euo pipefail
 
-echo '1..2'
-
 . $(dirname $0)/libtest.sh
 
+skip_without_ostree_httpd
+echo '1..2'
+
 V=$($CMD_PREFIX ostree --version | \
   python3 -c 'import sys, yaml; print(yaml.safe_load(sys.stdin)["libostree"]["Version"])')
 
diff --git a/tests/webserver.py b/tests/webserver.py
new file mode 100755 (executable)
index 0000000..95a083e
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+# Run a webserver on a random port, serving the current working directory.
+# The allocated port is written to the provided path
+import http.server
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+import socket
+import sys
+import os
+import argparse
+import threading
+import time
+import contextlib
+
+def _get_best_family(*address):
+    infos = socket.getaddrinfo(
+        *address,
+        type=socket.SOCK_STREAM,
+        flags=socket.AI_PASSIVE,
+    )
+    family, ty, proto, canonname, sockaddr = next(iter(infos))
+    return family, sockaddr
+
+def run(port_path, HandlerClass=SimpleHTTPRequestHandler,
+        ServerClass=ThreadingHTTPServer,
+        protocol="HTTP/1.1", port=0, bind=None):
+    ServerClass.address_family, addr = _get_best_family(bind, port)
+    HandlerClass.protocol_version = protocol
+
+    server = ThreadingHTTPServer(addr, HandlerClass)
+
+    with server as httpd:
+        host, port = httpd.socket.getsockname()[:2]
+        with open(port_path + '.tmp', 'w') as f:
+            f.write(f'{port}\n')
+        os.rename(port_path + '.tmp', port_path)
+        print(f"Running on port {port}")
+        try:
+            httpd.serve_forever()
+        except KeyboardInterrupt:
+            print("\nKeyboard interrupt received, exiting.")
+            sys.exit(0)
+
+def main():
+    # We automatically daemonize
+    pid = os.fork()
+    if pid > 0:
+        sys.exit(0)
+    os.setsid()
+    pid = os.fork()
+    if pid > 0:
+        sys.exit(0)
+    # code to spawn a thread and detect when the current working directory no longer exists, then exit
+    def watch_cwd(original_cwd):
+        while True:
+            try:
+                os.stat(original_cwd)
+            except FileNotFoundError:
+                sys.exit(0)
+            time.sleep(1)
+
+    original_cwd = os.getcwd()
+    watcher_thread = threading.Thread(target=watch_cwd, args=(original_cwd,))
+    watcher_thread.daemon = True
+    watcher_thread.start()
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('port_path', metavar='PATH',
+                        help='Write port used to this path ')
+    args = parser.parse_args()
+    run(args.port_path)
+
+if __name__ == '__main__':
+    main()